package e.edit;

import java.awt.*;
import java.awt.image.*;
import java.util.*;
import javax.swing.*;

/**
 * Displays a textual watermark in a view port. To use this, you
 * need to have a component in a JScrollPane with an instance of
 * this class as its view port. The component itself must be
 * non-opaque, or it'll overwrite our watermark.
 */
public class WatermarkViewPort extends JViewport {
    /**
     * A cache of all the watermark images we use.  This allows us to avoid
     * regenerating the images every time we need them, and we can re-use the
     * same image for several watermarks, saving time and memory.
     * The map is keyed on a weird string encoding the serious and non-serious
     * messages, and is generated by the getKeyFromMessages method.
     */
    private static final Map<String, BufferedImage> watermarkImageCache = new HashMap<String, BufferedImage>();
    
    /**
     * A light gray.
     * It's important that this isn't too dark, because users can reasonably expected to want to read files they can't edit.
     */
    private static final Color NON_SERIOUS_COLOR = new Color(210, 210, 210);
    
    /**
     * A light red. Okay, so it's pink. It doesn't mean anything.
     * It doesn't matter so much if this harms readability, because the user needs to make a decision about the problem that's being reported.
     */
    private static final Color SERIOUS_COLOR = new Color(250, 150, 150);
    
    /**
     * The image currently being tiled across the background.
     */
    private BufferedImage watermark;
    
    public WatermarkViewPort() {
    }
    
    /**
     * Sets the watermark string; ensures the view has the appropriate opacity.
     */
    public void setWatermark(String seriousMessage, String nonSeriousMessage) {
        BufferedImage newWatermark = getWatermark(seriousMessage, nonSeriousMessage);
        if (watermark != newWatermark) {
            watermark = newWatermark;
            JComponent view = (JComponent) getView();
            view.setOpaque(watermark == null);
            repaint();
        }
    }
    
    private synchronized BufferedImage getWatermark(String seriousMessage, String nonSeriousMessage) {
        seriousMessage = (seriousMessage == null) ? "" : seriousMessage;
        nonSeriousMessage = (nonSeriousMessage == null) ? "" : nonSeriousMessage;
        String key = getKeyFromMessages(seriousMessage, nonSeriousMessage);
        if (!watermarkImageCache.containsKey(key)) {
            watermarkImageCache.put(key, createWatermark(seriousMessage, nonSeriousMessage));
        }
        return watermarkImageCache.get(key);
    }
    
    private BufferedImage createWatermark(String seriousMessage, String nonSeriousMessage) {
        if ((seriousMessage.length() == 0) && (nonSeriousMessage.length() == 0)) {
            return null;
        }
        FontMetrics metrics = getFontMetrics(getFont());
        int fontHeight = metrics.getMaxAscent() + metrics.getMaxDescent();
        int seriousWidth = metrics.stringWidth(seriousMessage);
        int nonSeriousWidth = metrics.stringWidth(nonSeriousMessage);
        int maxStringWidth = Math.max(seriousWidth, nonSeriousWidth);
        // Not entirely accurate, but gives us some nice spacing.
        int imageSize = (int)((fontHeight * 2 + maxStringWidth) / Math.sqrt(2));
        BufferedImage image = new BufferedImage(imageSize, imageSize, BufferedImage.TYPE_INT_RGB);
        Graphics2D g = image.createGraphics();
        g.setColor(getView().getBackground());
        g.fillRect(0, 0, imageSize, imageSize);
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g.translate(imageSize / 2, imageSize / 2);
        g.rotate(-Math.PI / 4);
        g.setColor(SERIOUS_COLOR);
        g.drawString(seriousMessage, -seriousWidth / 2, 0);
        g.setColor(NON_SERIOUS_COLOR);
        g.drawString(nonSeriousMessage, -nonSeriousWidth / 2, fontHeight);
        return image;
    }
    
    /**
     * Returns a key for use in our internal HashMap.
     * The format's not important, only that only identical combinations of strings can map to the same key.
     */
    private String getKeyFromMessages(String seriousMessage, String nonSeriousMessage) {
        StringBuilder result = new StringBuilder();
        result.append(seriousMessage.length()).append(':').append(seriousMessage);
        result.append(nonSeriousMessage);
        return result.toString();
    }
    
    public void paintComponent(final Graphics g) {
        super.paintComponent(g);
        if (watermark != null) {
            paintWatermark(g);
        }
    }
    
    public void paintWatermark(final Graphics g) {
        Rectangle clip = g.getClipBounds();
        final int width = watermark.getWidth();
        final int height = watermark.getHeight();
        final int startX = clip.x - (clip.x % width);
        final int startY = clip.y - (clip.y % height);
        
        for (int x = startX; x < clip.x + clip.width; x += width) {
            for (int y = startY; y < clip.y + clip.height; y += height) {
                g.drawImage(watermark, x, y, null);
            }
        }
    }
}
